package org.uca.dss.curso1011.grupo9;

import java.util.HashMap;
import java.util.Map;
import com.db4o.*;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.uca.dss.curso1011.grupo9.basededatos.DBUtils;
import org.uca.dss.curso1011.grupo9.interfazExtendido.InformacionTrayecto;
import org.uca.dss.curso1011.grupo9.interfazExtendido.ReservaTrayecto;

/**
 * Gestor de Reservas
 * @author eldruin
 */
public class GestorReservas {
    private Map<Integer, Reserva> reservas;        //(identificador, objeto)
    // (id_viaje+fecha - numero_reservas_viaje)
    private Map<String, Integer> numeroReservas;
    private int siguienteCodigo;
    
    /**
     * Constructor.
     * Inicializa la base de datos
     */
    public GestorReservas ()
    {
        // Sacar de la BD
        reservas = new HashMap<Integer, Reserva>();

        numeroReservas = new HashMap<String, Integer> ();
        siguienteCodigo = 0;
        DBUtils.initDataBase("base_datos.db4o");
    }

    public static void init() throws Exception {
    }

    /**
     * Carga las reservas almacenadas en la base de datos
     */
    public void cargarReservas()
    {
        // Leer reservas de la base de datos
        ObjectContainer db = DBUtils.getDb();
            Reserva prototipo = new Reserva (0, null, 0);
            ObjectSet resultado = db.queryByExample(prototipo);
            while (resultado.hasNext())
            {
                Reserva reserva = (Reserva) resultado.next();
                guardarReserva(reserva, false);
            }
    }

    /**
     * Crea una reserva para el viaje y la fecha dados.
     * Es privado pero es necesario que sea público para ejecutar tests
     * Asigna -1 a todos los asientos
     * @param viaje
     * @param fecha
     * @return código de la reserva. 0 si no se ha realizado
     */
    public int crearReserva (Viaje viaje, LocalDate fecha)
    {
        Viaje nuevo = new Viaje(viaje);
        nuevo.setFechaInicio(fecha);
        if (plazasLibres(nuevo) > 0)
        {
            int siguiente_codigo = getSiguienteCodigo();
	    // de momento asignamos -1 al código del asiento para todos
            Reserva reserva = new Reserva(siguiente_codigo, nuevo,
					  -1);
            guardarReserva(reserva, true);

            return siguiente_codigo;
        }


        return 0;
    }

    /**
     * Guarda una reserva pasada en el sistema.
     * Opcionalmente la guarda en la base de datos
     * @param reserva reserva a guardar
     * @param store si es verdadero, almacena la reserva en la base de datos además
     */
    private void guardarReserva(Reserva reserva, boolean store)
    {
        if (reserva.getViaje() != null)
        {
            reservas.put(reserva.getCodigo(), reserva);
            incrementarReservas(reserva.getViaje().codigo());
            if (store)
            {
                // Guardar reserva en la base de datos
                ObjectContainer db = DBUtils.getDb();
                db.store(reserva);
            }
        }
    }

    /**
     * Cancela la reserva para un código dado
     * @param codigo como int
     * @return si se ha cancelado o no
     */
    public boolean cancelarReserva(int codigo)
    {
        if (reservas.containsKey(codigo))
        {
            Reserva reserva = reservas.get(codigo);
            decrementarReservas(reserva.getViaje().codigo());

            reservas.remove(codigo);
            // Eliminar reserva de la base de datos
            ObjectContainer db = DBUtils.getDb();
            ObjectSet resultado = db.queryByExample(new Reserva(reserva.getCodigo(), null, 0));
            Reserva encontrada = (Reserva) resultado.next();
            db.delete(encontrada);

            return true;
        }
        return false;
    }

    /**
     * @return código para la siguiente reserva
     */
    private int getSiguienteCodigo() {
        siguienteCodigo++;
        return siguienteCodigo;
    }

    /**
     * Número de plazas libres para un viaje.
     * Genérico respecto al número de trayectos
     * @param viaje
     * @return número de plazas libres del viaje
     */
    public int plazasLibres(Viaje viaje)
    {
        if (viaje != null && viaje.getTrayectos().size() <= 1)
            return (viaje.numeroAsientosDisponibles() -
                    numeroReservasViaje(viaje.codigo()));
        
        if (viaje != null && viaje.getTrayectos().size() > 1)
        {
            int plazas = viaje.numeroAsientosDisponibles();
            int maximo_reservas = 0;
            for (Trayecto t : viaje.getTrayectos())
            {
                int num_reservas = numeroReservasViaje(t.getCiudadOrigen(),
                                                       t.getCiudadDestino(),
                                                       viaje.getFechaInicio(),
                                                       t.getHoraSalida());
                if(num_reservas > maximo_reservas)
                    maximo_reservas = num_reservas;
            }

            return (viaje.numeroAsientosDisponibles() - maximo_reservas);
        }

        return -1;
    }

    /**
     * Número de reservas para un viaje
     * @param codigo
     * @return número de reservas.
     */
    private int numeroReservasViaje(String codigo)
    {
        if (numeroReservas.containsKey(codigo))
            return numeroReservas.get(codigo);

        return 0;
    }

    /**
     * Número de reservas para un viaje buscando por los datos.
     * @param origen
     * @param destino
     * @param fecha
     * @param hora
     * @return número de reservas
     */
    private int numeroReservasViaje(String origen, String destino, LocalDate fecha, LocalTime hora)
    {
        for (Map.Entry<Integer, Reserva> entry : reservas.entrySet())
        {
            Viaje v = entry.getValue().getViaje();

            if (v.ciudadOrigen().equals(origen) &&
                v.ciudadDestino().equals(destino) &&
                v.getFechaInicio().equals(fecha) &&
                v.horaSalida().equals(hora))
                return numeroReservasViaje(v.codigo());
        }

        return 0;
    }

    /**
     * Incrementa el número de reservas para un código de reserva
     * @param codigo
     * @return si se ha incrementado el número de reservas (true) o se almacena nuevo (false)
     */
    private boolean incrementarReservas(String codigo)
    {
        if (numeroReservas.containsKey(codigo))
        {
            int numero_reservas_viaje = numeroReservas.get(codigo);
            numero_reservas_viaje++;

            numeroReservas.put(codigo, numero_reservas_viaje);
            return true;
        }
        else
            numeroReservas.put(codigo, 1);

        return false;
    }

    /**
     * Decrementa el número de reservas para un código
     * @param codigo
     * @return si se decrementa el número de reservas o no
     */
    private boolean decrementarReservas(String codigo)
    {
        if (numeroReservas.containsKey(codigo))
        {
            int numero_reservas_viaje = numeroReservas.get(codigo);
            if (numero_reservas_viaje != 0)
                numero_reservas_viaje--;

            numeroReservas.put(codigo, numero_reservas_viaje);
            return true;
        }

        return false;
    }

    /**
     * @return número de reservas en el sistema
     */
    public int numeroReservas()
    {
        return reservas.size();
    }

    /**
     * Reserva un asiento de un viaje en una fecha
     * @param v
     * @param fecha
     * @return número de la reserva
     */
    public String reservaAsiento(Viaje v, LocalDate fecha)
    {
        return Integer.toString(crearReserva(v, fecha));
    }

    /**
     * Cancela una reserva a partir de un código
     * @param codigoReserva
     */
    public void cancelaReserva(String codigoReserva)
    {
        int codigo = Integer.parseInt(codigoReserva);
        cancelarReserva(codigo);
    }

    /**
     * Devuelve la lista de trayectos de una reserva como una lista de ReservaTrayecto
     * @param codigo
     * @return lista de reserva trayecto que compone la reserva
     */
    public List<ReservaTrayecto> reservaComoLista(String codigo)
    {
	Reserva r = getReserva(Integer.parseInt(codigo));
	Viaje v = r.getViaje();

	List<Trayecto> trayectos = v.getTrayectos();

	List<ReservaTrayecto> resultado = new ArrayList<ReservaTrayecto>();

	for (Trayecto t : trayectos)
	{
	    InformacionTrayecto i = new	InformacionTrayecto(t.getCiudadOrigen(),
                                                            t.getCiudadDestino(),
                                                            t.getHoraSalida(),
                                                            t.getHoraLlegada(),
                                                            t.calcularPrecio());
	    ReservaTrayecto rt = new ReservaTrayecto(i,
						     v.getFechaInicio(),
						     r.getNumeroAsiento(),
                                                     codigo);
	    resultado.add(rt);
	}

	return resultado;
    }

    /**
     * @param codigo
     * @return la reserva correspondiente al código
     */
    private Reserva getReserva(int codigo)
    {
	Reserva r = null;
	if (reservas.containsKey(codigo))
        {
	    r = reservas.get(codigo);
        }

	return r;
    }

}
